package com.youdoneed.common.service;

import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.querydsl.sql.SQLQueryFactory;
import com.youdoneed.base.application.AppException;
import com.youdoneed.base.common.GlobalIdWorker;
import com.youdoneed.base.common.GsonUtils;
import com.youdoneed.common.model.FileInfo;
import com.youdoneed.common.model.VideoScreenShotImg;
import com.youdoneed.common.query.QFileInfo;
import com.youdoneed.common.query.QVideoScreenShotImg;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_core.IplImage;
import org.bytedeco.javacv.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author Ewing
 * @since 2017-04-21
 **/
@Service
public class FileServiceImpl implements FileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileService.class);

    @Autowired
    private SQLQueryFactory queryFactory;
    private QFileInfo qFileInfo = QFileInfo.FileInfo;
    private QVideoScreenShotImg qVideoScreenShotImg = QVideoScreenShotImg.VideoScreenShotImg;

    public static final String VIDEO_DOWNLOAD_PATH = "resources/static/downloadvideo";
    //public static final String VIDEO_DOWNLOAD_PATH = "C:/Users/lenow/Desktop/videoCutFrame";

    @Value("${aliOss.accessKeyId}")
    private String accessKeyId;
    @Value("${aliOss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliOss.endpoint}")
    private String endpoint;
    @Value("${aliOss.bucket}")
    private String bucket;
    @Value("${aliOss.domain}")
    private String domain;

    private OSSClient ossClient;

    @PostConstruct
    public void initOssClient() {
        try {
            ClientConfiguration config = new ClientConfiguration();
            config.setMaxConnections(512);
            config.setSocketTimeout(10000);
            config.setMaxErrorRetry(3);
            ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret, config);
        } catch (Exception e) {
            LOGGER.error("初始化阿里云OSS客户端失败！", e);
            throw new AppException("初始化阿里云OSS客户端失败！", e);
        }
    }

    /**
     * 根据ID获取文件信息。
     */
    @Override
    @Transactional
    public FileInfo getFileInfo(String fileId) {
        if (!StringUtils.hasText(fileId)) {
            throw new AppException("文件ID不能为空！");
        }
        return queryFactory.selectFrom(qFileInfo)
                .where(qFileInfo.fileId.eq(fileId)).fetchOne();
    }

    /**
     * 根据ID批量获取文件信息。
     */
    @Override
    @Transactional
    public List<FileInfo> getFileInfos(String[] fileIds) {
        if (fileIds == null || fileIds.length == 0) {
            throw new AppException("文件ID不能为空！");
        }
        return queryFactory.selectFrom(qFileInfo)
                .where(qFileInfo.fileId.in(fileIds))
                .fetch();
    }

    /**
     * 根据MultipartFile创建文件信息。
     */
    private FileInfo createFileInfo(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new AppException("文件不能为空！");
        }
        FileInfo fileInfo = new FileInfo();
        String fileId = GlobalIdWorker.nextString();
        fileInfo.setFileId(fileId);
        // 获取原始文件名及格式
        String originalName = file.getOriginalFilename().trim();
        int lastIndex = originalName.lastIndexOf('.');
        String format = lastIndex > -1 ? originalName.substring(lastIndex + 1).trim() : "";
        fileInfo.setFormat(format);
        fileInfo.setOriginalName(originalName);
        // 文件名：fileId.format，若format为空，则为fileId
        String fileName = fileId + (format.length() > 0 ? '.' + format : format);
        fileInfo.setName(fileName);
        fileInfo.setSize(file.getSize());
        fileInfo.setUrl(domain + '/' + fileName);
        fileInfo.setCreateTime(new Date());
        return fileInfo;
    }

    /**
     * 保存文件并返回文件信息。
     */
    @Override
    @Transactional
    public FileInfo saveFile(MultipartFile file) {
        FileInfo fileInfo = createFileInfo(file);
        queryFactory.insert(qFileInfo)
                .populate(fileInfo)
                .execute();

        try {
            ossClient.putObject(bucket, fileInfo.getName(), file.getInputStream());
        } catch (Exception e) {
            LOGGER.error("阿里OSS保存文件失败。", e);
            throw new AppException("阿里OSS保存文件失败。", e);
        }
        return fileInfo;
    }

    /**
     * 删除文件，并返回被删除的文件信息。
     */
    @Override
    @Transactional
    public FileInfo deleteFile(String fileId) {
        if (!StringUtils.hasText(fileId)) {
            throw new AppException("文件ID不能为空！");
        }

        FileInfo fileInfo = queryFactory.selectFrom(qFileInfo)
                .where(qFileInfo.fileId.eq(fileId)).fetchOne();

        if (fileInfo == null) {
            throw new AppException("文件不存在或已被删除！");
        }

        queryFactory.delete(qFileInfo)
                .where(qFileInfo.fileId.eq(fileId)).execute();

        try {
            ossClient.deleteObject(bucket, fileInfo.getName());
        } catch (Exception e) {
            LOGGER.error("阿里OSS删除文件失败。", e);
            throw new AppException("阿里OSS删除文件失败。", e);
        }
        return fileInfo;
    }

    @Override
    @Transactional
    public FileInfo deleteByUrl(String url) {
        if (!StringUtils.hasText(url)) {
            throw new AppException("文件URL不能为空！");
        }

        FileInfo fileInfo = queryFactory.selectFrom(qFileInfo)
                .where(qFileInfo.url.eq(url.trim())).fetchOne();

        if (fileInfo == null) {
            throw new AppException("文件不存在或已被删除！");
        }

        queryFactory.delete(qFileInfo).where(
                qFileInfo.fileId.eq(fileInfo.getFileId())).execute();

        try {
            ossClient.deleteObject(bucket, fileInfo.getName());
        } catch (Exception e) {
            LOGGER.error("阿里OSS删除文件失败。", e);
            throw new AppException("阿里OSS删除文件失败。", e);
        }
        return fileInfo;
    }

    /**
     * 视频截帧并保存
     *
     * @param videoUrl 视频url
     * @param videoId  视频id,对应forum_message_id
     * @param musicUrl 音乐url
     * @return
     * @throws FrameGrabber.Exception
     * @author hexz
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public long videoCutFrame(String videoUrl, String videoId, String musicUrl) throws FrameGrabber.Exception {
        long startTime = System.currentTimeMillis();
        List<String> imgUrls = new ArrayList<>();
        File video = null;
        try {
            URL url = new URL(videoUrl);
            File directory = new File(VIDEO_DOWNLOAD_PATH);
            //下载视频文件
            video = Loader.extractResource(url, directory, null, null);
            LOGGER.info("文件名===" + video.getName());
            imgUrls = grabberFFmpegImage(videoId, video);
        } catch (Exception e) {
            LOGGER.info("视频截帧失败");
            throw new AppException("视频截帧失败");
        } finally {
            //删除下载的视频文件
            if (video.exists()) {
                video.delete();
            }
        }
        //保存url数组到数据库
        long sucNum = 0;
        if (null != imgUrls && imgUrls.size() > 0) {
            VideoScreenShotImg videoScreenShotImg =
                    new VideoScreenShotImg();
            videoScreenShotImg.setImageId(GlobalIdWorker.nextString());
            videoScreenShotImg.setImageName("视频截帧数组");
            videoScreenShotImg.setImageUrl(GsonUtils.toJson(imgUrls));
            videoScreenShotImg.setVideoId(videoId);
            videoScreenShotImg.setFormat(".jpg");
            videoScreenShotImg.setSize(imgUrls.size());
            videoScreenShotImg.setMusicUrl(musicUrl);
            videoScreenShotImg.setCreateTime(new Timestamp(System.currentTimeMillis()));

            sucNum = queryFactory.insert(qVideoScreenShotImg)
                    .populate(videoScreenShotImg)
                    .execute();
        }

        long endTime = System.currentTimeMillis();
        LOGGER.info("------耗时-------" + (endTime - startTime));
        return sucNum;

    }

    /**
     * 视频截帧
     *
     * @param videoId 视频id
     * @param video   视频文件
     * @return
     * @throws Exception
     * @author hexz
     */
    private List<String> grabberFFmpegImage(String videoId, File video)
            throws Exception {
        //处理物理内存过大异常
        System.setProperty("org.bytedeco.javacpp.maxphysicalbytes", "0");
        System.setProperty("org.bytedeco.javacpp.maxbytes", "0");

        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(video);
        LOGGER.info("------开始截帧------");
        grabber.start();
        //获取旋转信息
        String rotate = grabber.getVideoMetadata("rotate");
        //获取帧数
        int frameLength = grabber.getLengthInFrames();
        LOGGER.info("------帧数------" + frameLength);

        Frame frame;
        int index = 0;
        List<String> imgUrls = new ArrayList<>();
        InputStream inputStream = null;
        try {
            //截取每帧图片
            while (index < frameLength) {
                frame = grabber.grabImage();
                if (null == frame || null == frame.image) {
                    index++;
                    continue;
                }
                //有旋转信息则处理
                if (null != rotate && rotate.length() > 1) {
                    OpenCVFrameConverter.ToIplImage converter =
                            new OpenCVFrameConverter.ToIplImage();
                    IplImage iplImage = converter.convert(frame);
                    frame = converter.convert(rotate(iplImage, Integer.valueOf(rotate)));
                }
                //获取文件流
                inputStream = convertStream(frame);
                //图片名称：视频id+"-"+序号
                String fileName = videoId + "-" + (index + 1) + ".jpg";
                String imgUrl = domain + "/" + fileName;
                imgUrls.add(imgUrl);
                ossClient.putObject(bucket, fileName, inputStream);
                index++;
            }

        } catch (Exception e) {
            LOGGER.error("视频截帧失败。", e);
            throw new AppException("视频截帧失败。", e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
        grabber.close();
        LOGGER.info("------截帧完成------");
        return imgUrls;
    }

    /**
     * 旋转图片
     *
     * @param iplImage
     * @param angle
     * @return
     * @author hexz
     */
    private IplImage rotate(IplImage iplImage, int angle) {
        IplImage image = IplImage.create(iplImage.height(),
                iplImage.width(), iplImage.depth(), iplImage.nChannels());
        opencv_core.cvTranspose(iplImage, image);
        opencv_core.cvFlip(image, image, angle);
        return image;
    }

    /**
     * 转换图片流
     *
     * @param frame
     * @author hexz
     */
    private InputStream convertStream(Frame frame) {

        Java2DFrameConverter converter = new Java2DFrameConverter();

        String imageMat = "jpg";
        BufferedImage bi = converter.getBufferedImage(frame);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        //BufferedImage对象转InputStream
        try {
            ImageIO.write(bi, imageMat, outputStream);
            return new ByteArrayInputStream(outputStream.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取视频截图
     *
     * @param videoId 视频ID
     * @return
     * @author hexz
     */
    @Override
    @Transactional
    public VideoScreenShotImg getVideoScreenImgs(String videoId) {
        VideoScreenShotImg videoScreenShotImg =
                queryFactory.selectFrom(qVideoScreenShotImg)
                        .where(qVideoScreenShotImg.videoId.eq(videoId))
                        .fetchOne();
        //前端按约定规则直接加载,仅从后台取图片数量
        if(videoScreenShotImg != null){
            videoScreenShotImg.setImageUrl("");
        }
        return videoScreenShotImg;
    }

}
